Skip to content

Conversation

@harsh-akamai
Copy link
Contributor

Changes 🔄

Added queries for the following marketplace endpoints

  • GET /marketplace/products
  • GET /marketplace/products/{productId}
  • GET /marketplace/categories
  • GET /marketplace/types
  • POST /marketplace/referral

Scope 🚢

Upon production release, changes in this PR will be visible to:

  • All customers
  • Some customers (e.g. in Beta or Limited Availability)
  • No customers / Not applicable

How to test 🧪

Verification steps

  • Verify the queries call the right apis and keys.
Author Checklists

As an Author, to speed up the review process, I considered 🤔

👀 Doing a self review
❔ Our contribution guidelines
🤏 Splitting feature into small PRs
➕ Adding a changeset
🧪 Providing/improving test coverage
🔐 Removing all sensitive information from the code and PR description
🚩 Using a feature flag to protect the release
👣 Providing comprehensive reproduction steps
📑 Providing or updating our documentation
🕛 Scheduling a pair reviewing session
📱 Providing mobile support
♿ Providing accessibility support


  • I have read and considered all applicable items listed above.

As an Author, before moving this PR from Draft to Open, I confirmed ✅

  • All tests and CI checks are passing
  • TypeScript compilation succeeded without errors
  • Code passes all linting rules

@harsh-akamai harsh-akamai self-assigned this Jan 8, 2026
@harsh-akamai harsh-akamai marked this pull request as ready for review January 8, 2026 04:06
@harsh-akamai harsh-akamai requested a review from a team as a code owner January 8, 2026 04:06
Copy link
Contributor

@pmakode-akamai pmakode-akamai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unrelated to this PR, but it might be worth fixing here: I noticed the factory mocks & Types don't fully match the API doc

import type { Filter, Params } from '@linode/api-v4';

export const marketplaceQueries = createQueryKeys('marketplace', {
product: (productId: number) => ({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now the api has changed from /marketplace/products/{id} to /marketplace/products/{id}/details

@tvijay-akamai
Copy link
Contributor

This is unrelated to this PR, but it might be worth fixing here: I noticed the factory mocks & Types don't fully match the API doc

Let's verify all the fields for all the apis as per this doc: https://docs.google.com/document/d/1Iw5nUpUwtofBz5pSnr7j5jXPMBlncFVdyfzLYfnN0NI/edit?tab=t.0

queryKey: [filter],
}),
paginated: (params: Params = {}, filter: Filter = {}) => ({
queryFn: () => getMarketplaceTypes(params, filter),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be getMarketplacePartners, instead of getMarketplaceTypes

infinite: (filter: Filter = {}) => ({
queryFn: ({ pageParam }) =>
getMarketplaceProducts(
{ page: pageParam as number, page_size: 25 },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should page size be 25 for products api also?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minimum size for a page is 25

filter: Filter,
enabled: boolean = false,
) => {
useQuery<ResourcePage<MarketplacePartner>, APIError[]>({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to have a return statement before useQuery

MarketplaceCategory,
MarketplacePartner,
MarketplacePartnerReferralPayload,
MarketplaceProduct,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's separate types from value imports and use import type { way instead for importing the types

placeholderData: keepPreviousData,
});
};

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no types defined in here like (like useMarketplaceTypesQuery, useAllMarketplaceTypesQuery, etc.). In keys we have queries defined for types api though. Hook should be added here for types.

export const useMarketplaceProductsQuery = (
params: Params,
filter: Filter,
enabled: boolean = false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we defaulting enabled to false? it means queries won't run unless explicitly enabled.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I'd prefer to keep it this way until we have the feature flag. I'm open to changing the value if needed

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd lean towards keeping the default as true and controlling this query by passing enabled from the consumer. That way, we only disable it when the feature flag is off, which is consistent with how we handle this in other places and is technically safer

Copy link
Contributor

@tvijay-akamai tvijay-akamai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@harsh-akamai This looks good to me. Thanks. Approved.

@tvijay-akamai tvijay-akamai added the Add'tl Approval Needed Waiting on another approval! label Jan 9, 2026
partner_id: number;
product_tags?: string[];
short_description: string;
title_tag?: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
title_tag?: string;
tile_tag?: string;

This should be tile_tag

}

export interface MarketplaceCategory {
category: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
category: string;
name: string;

As per API doc, I think this should be name

created_at: string;
created_by: string;
id: number;
logo_url_dark_mode?: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
logo_url_dark_mode?: string;
logo_url_dark_mode: string;

I don't this this should be an optional field

@@ -0,0 +1,197 @@
import { APIError, createPartnerReferral, ResourcePage } from '@linode/api-v4';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's separate type imports from function imports

import { createPartnerReferral } from '@linode/api-v4';
import type { APIError, ResourcePage } from '@linode/api-v4';

export const useMarketplaceProductsQuery = (
params: Params,
filter: Filter,
enabled: boolean = false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd lean towards keeping the default as true and controlling this query by passing enabled from the consumer. That way, we only disable it when the feature flag is off, which is consistent with how we handle this in other places and is technically safer

export const marketplaceCategoryFactory =
Factory.Sync.makeFactory<MarketplaceCategory>({
id: Factory.each((id) => id),
category: Factory.each((id) => `marketplace-category-${id}`),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
category: Factory.each((id) => `marketplace-category-${id}`),
name: Factory.each((id) => `marketplace-category-${id}`),

product_tags: ['tag1', 'tag2'],
short_description:
'This is a short description of the marketplace product.',
title_tag: 'Marketplace Product Title Tag',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
title_tag: 'Marketplace Product Title Tag',
tile_tag: 'New',

or

Suggested change
title_tag: 'Marketplace Product Title Tag',
tile_tag: '60 days free trial',

created_at: '2024-01-01T00:00:00',
created_by: 'user1',
id: Factory.each((id) => id),
logo_url_dark_mode: 'https://www.example.com/logo-night-mode.png',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
logo_url_dark_mode: 'https://www.example.com/logo-night-mode.png',
logo_url_dark_mode: 'https://www.example.com/logo-dark-mode.png',

minor nit

@@ -1,42 +1,59 @@
export interface MarketplaceProductDetail {
documentation: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
documentation: string;
documentation?: string;

documentation appears to be an optional field as well

category_ids: number[];
created_at: string;
created_by: string;
details?: MarketplaceProductDetail;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a blocker, but I don't see details marked as optional anywhere. Still it's worth checking

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Details is the part of the /products/{id}/details API only. Every other field is returned from both /products and /products/{id}/details

@linode-gh-bot
Copy link
Collaborator

Cloud Manager UI test results

🔺 1 failing test on test run #7 ↗︎

❌ Failing✅ Passing↪️ Skipped🕐 Duration
1 Failing854 Passing11 Skipped40m 53s

Details

Failing Tests
SpecTest
clone-linode.spec.tsCloud Manager Cypress Tests→clone linode » can clone a Linode from Linode details page

Troubleshooting

Use this command to re-run the failing tests:

pnpm cy:run -s "cypress/e2e/core/linodes/clone-linode.spec.ts"

Copy link
Contributor

@pmakode-akamai pmakode-akamai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks @harsh-akamai for making the suggested changes - looks good to me. Could you check if we need to add a changeset for the new changes to the apiv4 package?

@github-project-automation github-project-automation bot moved this from Review to Approved in Cloud Manager Jan 9, 2026
@tvijay-akamai tvijay-akamai removed the Add'tl Approval Needed Waiting on another approval! label Jan 9, 2026
@tvijay-akamai tvijay-akamai merged commit 25a4879 into linode:develop Jan 9, 2026
34 of 35 checks passed
@github-project-automation github-project-automation bot moved this from Approved to Merged in Cloud Manager Jan 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Merged

Development

Successfully merging this pull request may close these issues.

4 participants